﻿using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;

namespace MJTech.AspNetCore.Authentication.WechatMp
{
    public class WeChatMpAutenticationHandler : OAuthHandler<WeChatMpOptions>
    {
        private WeChatMpOptions WeChatOptions;

        private readonly ISecureDataFormat<AuthenticationProperties> _secureDataFormat;

        public WeChatMpAutenticationHandler(IOptionsMonitor<WeChatMpOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, ISecureDataFormat<AuthenticationProperties> secureDataFormat) : base(options, logger, encoder, clock)
        {
            this.WeChatOptions = options.CurrentValue;
            this._secureDataFormat = secureDataFormat;
        }


        protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(OAuthCodeExchangeContext context)
        {

            Dictionary<string, string> queryString = new Dictionary<string, string>
            {
                {
                    "appid",
                    Options.ClientId
                },
                {
                    "secret",
                    Options.ClientSecret
                },
                {
                    "code",
                    context.Code.Trim()
                },
                {
                    "grant_type",
                    "authorization_code"
                }
            };
            string requestUri = QueryHelpers.AddQueryString(Options.TokenEndpoint, queryString);

            HttpResponseMessage httpResponseMessage = await Backchannel.GetAsync(requestUri, Context.RequestAborted);
            OAuthTokenResponse result;
            if (httpResponseMessage.IsSuccessStatusCode)
            {
                string jsonString = await httpResponseMessage.Content.ReadAsStringAsync();
                result = OAuthTokenResponse.Success(JsonDocument.Parse(jsonString, default(JsonDocumentOptions)));
            }
            else
            {
                result = OAuthTokenResponse.Failed(new Exception("wechat-mpExchangeCodeAsync:远程获取WeChat令牌失败"));
            }
            return result;
        }


        protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync()
        {
            IQueryCollection query = Request.Query;
            string text = query["code"];
            HandleRequestResult result;
            try
            {
                if (string.IsNullOrEmpty(text))
                {
                    result = HandleRequestResult.Fail("WeChat Code获取失败");
                }
                else
                {
                    string protectedText = query["oauthstate"];
                    string state = query["state"];
                    AuthenticationProperties properties = null;
                    properties = this._secureDataFormat.Unprotect(protectedText);

                    if (state != Options.StateAddition || properties == null)
                    {
                        result = HandleRequestResult.Fail("WeChat远程登录验证失败");
                    }
                    else if (!this.ValidateCorrelationId(properties))
                    {
                        result = HandleRequestResult.Fail("WeChat CSRF攻击");
                    }
                    else if (string.IsNullOrEmpty(Options.CallbackPath))
                    {
                        result = HandleRequestResult.Fail("回调地址未设置");
                    }
                    else
                    {
                        string redirectUri = BuildRedirectUri(Options.CallbackPath);
                        OAuthTokenResponse oauthTokenResponse = await ExchangeCodeAsync(new OAuthCodeExchangeContext(properties, text, redirectUri));

                        if (oauthTokenResponse.Error != null)
                        {
                            result = HandleRequestResult.Fail(oauthTokenResponse.Error.Message);
                        }
                        else if (string.IsNullOrEmpty(oauthTokenResponse.AccessToken))
                        {
                            result = HandleRequestResult.Fail("未能获取到WeChat access token.");
                        }
                        else
                        {
                            ClaimsIdentity identity = new ClaimsIdentity(ClaimsIssuer);
                            AuthenticationTicket authenticationTicket = await CreateTicketAsync(identity, properties, oauthTokenResponse);

                            Logger.LogInformation("wechatmp-Ticket:{$authenticationTicket}", authenticationTicket);

                            if (authenticationTicket != null)
                            {
                                result = HandleRequestResult.Success(authenticationTicket);
                            }
                            else
                            {
                                result = HandleRequestResult.Fail("创建身份认证票据失败");
                            }
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "wechat-mp exception");
                result = null;
            }
            return result;
        }


        protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
        {
            string result = System.Text.Json.JsonSerializer.Serialize(properties);
            string str = this._secureDataFormat.Protect(properties);
            redirectUri = redirectUri + "?oauthstate=" + str;
            QueryBuilder queryBuilder = new QueryBuilder
            {
                {
                    "appid",
                    base.Options.ClientId
                },
                {
                    "redirect_uri",
                    redirectUri
                },
                {
                    "response_type",
                    "code"
                },
                {
                    "scope",
                    this.WeChatOptions.WeChatScope
                },
                {
                    "state",
                    base.Options.StateAddition
                }
            };
            return base.Options.AuthorizationEndpoint + queryBuilder.ToString();
        }


        protected async Task<JsonDocument> GetUserInfoAsync(OAuthTokenResponse tokens)
        {
            Dictionary<string, string> queryString = new Dictionary<string, string>
            {
                {
                    "openid",
                    "openid"
                },
                {
                    "access_token",
                    tokens.AccessToken
                },
                {
                    "lang",
                    "zh-CN"
                }
            };
            string requestUri = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, queryString);
            HttpResponseMessage httpResponseMessage = await Backchannel.GetAsync(requestUri, Context.RequestAborted);
            if (!httpResponseMessage.IsSuccessStatusCode)
            {
                throw new Exception("未能获取到WeChat登录的用户个人信息");
            }
            return JsonDocument.Parse(await httpResponseMessage.Content.ReadAsStringAsync(), default(JsonDocumentOptions));
        }


        protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
        {
            JsonDocument jsonDocument = await this.GetUserInfoAsync(tokens);
            string value = jsonDocument.RootElement.GetString("openid")?.Trim();
            identity.AddClaim(new Claim("sub", value));
            string value2 = jsonDocument.RootElement.GetString("nickname")?.Trim();
            identity.AddClaim(new Claim("nickname", value2));
            string value3 = jsonDocument.RootElement.GetString("sex")?.Trim();
            identity.AddClaim(new Claim("sex", value3 ?? "0"));
            string value4 = jsonDocument.RootElement.GetString("city")?.Trim();
            identity.AddClaim(new Claim("city", value4 ?? ""));
            string value5 = jsonDocument.RootElement.GetString("province")?.Trim();
            identity.AddClaim(new Claim("province", value5 ?? ""));
            string value6 = jsonDocument.RootElement.GetString("country")?.Trim();
            identity.AddClaim(new Claim("country", value6 ?? ""));
            string value7 = jsonDocument.RootElement.GetString("headimgurl")?.Trim();
            identity.AddClaim(new Claim("headimgurl", value7 ?? ""));
            string value8 = jsonDocument.RootElement.GetString("unionid")?.Trim();
            identity.AddClaim(new Claim("unionid", value8 ?? ""));

            OAuthCreatingTicketContext context = new OAuthCreatingTicketContext(
                    new ClaimsPrincipal(identity),
                    properties, this.Context, this.Scheme, this.Options, this.Backchannel,
                    tokens, jsonDocument.RootElement);

            context.RunClaimActions();
            await Events.CreatingTicket(context);
            return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);

        }
    }
}
